namespace TomatoBreak.DomainObjects
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Globalization;
    using System.Security;
    using System.Security.Cryptography;
    using System.Text;

    using Infrastructure;

    [DebuggerDisplay("{UserName}")]
    public class User : Entity
    {
        private string userName;
        private string password;
        private string salt;
        private string email;
        private DateTime createdAt;
        private IList<Task> tasks;
        private IList<ToDoList> toDoLists;

        [Obsolete("Used by the ORM")]
        public User()
        {
        }

        public User(string userName, string password, string email)
        {
            if (string.IsNullOrWhiteSpace(userName))
            {
                throw new ArgumentNullException("userName");
            }

            if (string.IsNullOrWhiteSpace(password))
            {
                throw new ArgumentNullException("password");
            }

            if (string.IsNullOrWhiteSpace(email))
            {
                throw new ArgumentNullException("email");
            }

            this.userName = userName;
            this.password = UpdatePassword(password);
            this.email = email.ToLower(CultureInfo.CurrentCulture);

            createdAt = Clock.UtcNow();
        }

        public virtual string UserName
        {
            [DebuggerStepThrough]
            get { return userName; }

            [DebuggerStepThrough]
            protected set { userName = value; }
        }

        public virtual string Password
        {
            [DebuggerStepThrough]
            get { return password; }

            [DebuggerStepThrough]
            protected set { password = value; }
        }

        public virtual string Salt
        {
            [DebuggerStepThrough]
            get { return salt; }

            [DebuggerStepThrough]
            protected set { salt = value; }
        }

        public virtual string Email
        {
            [DebuggerStepThrough]
            get { return email; }

            [DebuggerStepThrough]
            protected set { email = value; }
        }

        public virtual bool Locked { get; protected set; }

        public virtual DateTime CreatedAt
        {
            [DebuggerStepThrough]
            get { return createdAt; }

            [DebuggerStepThrough]
            protected set { createdAt = value; }
        }

        public virtual IEnumerable<Task> Tasks
        {
            [DebuggerStepThrough]
            get { return tasks ?? (tasks = new List<Task>()); }
        }

        public virtual IEnumerable<ToDoList> ToDoLists
        {
            [DebuggerStepThrough]
            get { return toDoLists ?? (toDoLists = new List<ToDoList>()); }
        }

        public virtual bool HasPassword(string plainPassword)
        {
            if (string.IsNullOrWhiteSpace(plainPassword))
            {
                throw new ArgumentNullException("plainPassword");
            }

            return HashPassword(plainPassword).Equals(Password);
        }

        public virtual void ChangePassword(
            string oldPassword, string newPassword)
        {
            if (string.IsNullOrWhiteSpace(newPassword))
            {
                throw new ArgumentNullException("newPassword");
            }

            if (!HasPassword(oldPassword))
            {
                throw new SecurityException(
                    ErrorMessages.User_ChangePassword_DoNotMatch);
            }

            Password = HashPassword(newPassword);
        }

        public virtual void Lock()
        {
            if (!Locked)
            {
                Locked = true;
            }
        }

        public virtual void Unlock()
        {
            if (Locked)
            {
                Locked = false;
            }
        }

        public virtual Task AddTask(string name, DateTime onDate)
        {
            Task task = new Task(this, name, onDate);

            ((ICollection<Task>)Tasks).Add(task);

            return task;
        }

        public virtual ToDoList AddToDoList(DateTime onDate)
        {
            ToDoList list = new ToDoList(this, onDate);

            ((ICollection<ToDoList>)ToDoLists).Add(list);

            return list;
        }

        private static string GenerateSalt()
        {
            var buffer = new byte[32];

            using (var generator = new RNGCryptoServiceProvider())
            {
                generator.GetBytes(buffer);
            }

            return Convert.ToBase64String(buffer);
        }

        private string UpdatePassword(string plain)
        {
            if (string.IsNullOrWhiteSpace(salt))
            {
                salt = GenerateSalt();
            }

            return HashPassword(plain);
        }

        private string HashPassword(string plain)
        {
            var passwordBytes = Encoding.Unicode.GetBytes(plain.Trim());
            var saltBytes = Convert.FromBase64String(salt);
            var combined = new byte[passwordBytes.Length + saltBytes.Length];

            Buffer.BlockCopy(saltBytes, 0, combined, 0, saltBytes.Length);

            Buffer.BlockCopy(
                passwordBytes,
                0,
                combined,
                saltBytes.Length,
                passwordBytes.Length);

            using (var hasher = HashAlgorithm.Create("SHA256"))
            {
                byte[] hash = hasher.ComputeHash(combined);

                return Convert.ToBase64String(hash);
            }
        }
    }
}